package cn.ihoway.seg;

import cn.ihoway.analysis.DocAnalyzer;
import cn.ihoway.entity.Dict;
import cn.ihoway.entity.DocInfo;
import cn.ihoway.entity.Index;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.HashMap;

/**
 * 对文档进行切词，并对词进行索引创建
 */
public class DocSeg {

    private static final int DIVIDE_SCALE = 12; //除法精度

    /**
     * 单个文档进行索引创建
     * @param doc 文档
     * @return 索引列表
     */
    public ArrayList<Index> singleDocSeg(DocInfo doc){
        DocAnalyzer analyzer = new DocAnalyzer();
        //切词 获取各词语在文档中的具体位置信息
        HashMap<String, ArrayList<String>> res = analyzer.docSeg(doc.getContent());
        int cnt = Integer.parseInt(res.get("$cnt").get(0));
        ArrayList<Index> indexList = new ArrayList<>();
        for(String word:res.keySet()) {
            Index index = getIndex(doc, res, cnt, word);
            indexList.add(index);
            //index.create(dir);
        }
        return indexList;
    }

    /**
     * 多个文档进行索引创建
     * @param docList 文档列表
     * @return 索引列表
     */
    public ArrayList<Index> multipleDocSeg(ArrayList<DocInfo> docList){
        ArrayList<Index> indexList = new ArrayList<>();
        for(DocInfo doc : docList){
            indexList.addAll(singleDocSeg(doc));
        }
        return indexList;
    }

    /**
     * 多线程创建索引
     * @param indexList 索引列表
     * @param dir 索引目录
     */
    public void createIndex(ArrayList<Index> indexList, String dir){
        DocSegThread dt = new DocSegThread();
        dt.setIndexList(indexList);
        dt.setDir(dir);
        int threadNum = 1; //线程数量应动态调整
        if(indexList.size() > 10){
            threadNum = 2;
        }
        if(indexList.size() > 100){
            threadNum = 5;
        }
        if(indexList.size() > 1000){
            threadNum = 10;
        }
        if(indexList.size() > 10000){
            threadNum = 50;
        }
        for(int i = 1;i <= threadNum;i++) {
            Thread thread = new Thread(dt,""+i);
            thread.start();
        }
    }


    private Index getIndex(DocInfo doc, HashMap<String, ArrayList<String>> res, int cnt, String word) {
        ArrayList<String> pos = res.get(word);
        BigDecimal tf = BigDecimal.valueOf(pos.size()).divide(BigDecimal.valueOf(cnt),DIVIDE_SCALE, RoundingMode.HALF_UP); //关键词在该文档中出现的频率（词语出现次数/该文档全部词语数量）
        //System.out.println("cnt:"+cnt+" pos size:"+pos.size() + " tf:"+tf);
        Index index = new Index();
        index.setTitle(doc.getTitle());
        index.setId(doc.getId());
        index.setPos(pos);
        index.setName(word);
        index.setLength(doc.getContent().length());
        index.setUrl(doc.getUrl());
        index.setTf(tf);
        return index;
    }

    /**
     * 测试多线程创建索引
     */
    public static void main(String[] args) {
        Dict.init(); //初始化数据字典
        DocInfo docInfo = new DocInfo();
        docInfo.setTitle("测试1");
        docInfo.setId("001");
        docInfo.setUrl("http://ihoway.cn");
        docInfo.setContent("自然语言处理是计算机科学领域与人工智能领域中的一个重要方向。" +
                "它研究能实现人与计算机之间用自然语言进行有效通信的各种理论和方法。" +
                "自然语言处理是一门融语言学、计算机科学、数学于一体的科学。" +
                "因此，这一领域的研究将涉及自然语言，即人们日常使用的语言，" +
                "所以它与语言学的研究有着密切的联系，但又有重要的区别。" +
                "自然语言处理并不是一般地研究自然语言，" +
                "而在于研制能有效地实现自然语言通信的计算机系统，" +
                "特别是其中的软件系统。因而它是计算机科学的一部分。");

        DocInfo docInfo2 = new DocInfo();
        docInfo2.setTitle("测试2");
        docInfo2.setId("002");
        docInfo2.setUrl("http://ihoway.cn");
        docInfo2.setContent("原因分析：\n" +
                "\n" +
                "1）参数类型为double的构造方法的结果有一定的不可预知性。有人可能认为在Java中写入newBigDecimal(0.1)所创建的BigDecimal正好等于 0.1（非标度值 1，其标度为 1），但是它实际上等于0.1000000000000000055511151231257827021181583404541015625。这是因为0.1无法准确地表示为 double（或者说对于该情况，不能表示为任何有限长度的二进制小数）。这样，传入到构造方法的值不会正好等于 0.1（虽然表面上等于该值）。\n" +
                "\n" +
                "2）String 构造方法是完全可预知的：写入 newBigDecimal(“0.1”) 将创建一个 BigDecimal，它正好等于预期的 0.1。因此，比较而言， 通常建议优先使用String构造方法。\n" +
                "\n" +
                "3）当double必须用作BigDecimal的源时，请注意，此构造方法提供了一个准确转换；它不提供与以下操作相同的结果：先使用Double.toString(double)方法，然后使用BigDecimal(String)构造方法，将double转换为String。要获取该结果，请使用static valueOf(double)方法。");


        DocInfo docInfo3 = new DocInfo();
        docInfo3.setTitle("测试3");
        docInfo3.setId("003");
        docInfo3.setUrl("http://ihoway.cn");
        docInfo3.setContent("总结\n" +
                "在需要精确的小数计算时再使用BigDecimal，BigDecimal的性能比double和float差，在处理庞大，复杂的运算时尤为明显。故一般精度的计算没必要使用BigDecimal。\n" +
                "尽量使用参数类型为String的构造函数。\n" +
                "BigDecimal都是不可变的（immutable）的， 在进行每一次四则运算时，都会产生一个新的对象 ，所以在做加减乘除运算时要记得要保存操作后的值。");

        ArrayList<DocInfo> docInfos = new ArrayList<>();
        docInfos.add(docInfo);
        docInfos.add(docInfo2);
        docInfos.add(docInfo3);

        DocSeg docSeg = new DocSeg();

        ArrayList<Index> indexList = docSeg.multipleDocSeg(docInfos);

        docSeg.createIndex(indexList,"测试");
    }

}
